# -*- coding: utf-8 -*-

#    OriginDB
#    Copyright (C) 2010  Jason Power <power.jg@gmail.com>
#    Copyright (C) 2010  Travis Gockel <travis@gockelhut.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import cgi
import uuid
import logging

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db

import dbmodel
import yamlhelper
import handler
import search
from mymodel import NotUniqueError, NotAuthorizedError


class MainHandler(handler.Handler):
    def get(self):
		# just send them to our Google Code project page
		self.response.set_status(301, "Moved permanently")
		self.response.headers.add_header("Location","http://code.google.com/p/origindb/")

class InsertHandler(handler.Handler):
	def post(self):

		if not self.requiredParams('type', 'sID'):
			logging.info("Required param missing in insert: "+str(self.params))
			return

		data = self.request.body

		typ = self.params['type']
		try:
			obj = dbmodel.types[typ].insert(data, self.user)
		except KeyError:
			logging.info("Invalid type given in insert: "+typ)
			self.response.set_status(400, "Invalid type, "+typ)
			return
		except NotUniqueError, e:
			logging.info("Key not unique: "+e.key)
			self.response.set_status(400, "Key not unique: "+e.key)
			return
		except NotAuthorizedError, e:
			logging.info("User not authorized to perform action:\n%s" % e)
			self.response.set_status(401, str(e))
			return
		
		self.response.out.write("---\n")
		self.response.out.write("key: ")
		self.response.out.write(str(obj.key()))
		self.response.set_status(200)

class GetHandler(handler.Handler):
	def get(self):
		if not self.requiredParams('type', 'key'):
			return
		
		try:
			key = db.Key(self.params['key'])
		except db.BadKeyError:
			self.response.set_status(400, "Key: "+self.params['key']+" invalid")
			return

		typ = self.params['type']
		try:
			obj = dbmodel.types[typ].get(key)
		except KeyError:
			self.response.set_status(400, "Invalid type, "+typ)
			return
		except db.KindError:
			self.response.set_status(404, "No %s with key %s" % (typ, self.params['key']))
			return
		
		self.response.out.write("---\n")
		if (obj):
			self.response.out.write(obj.dump())
		else:
			self.response.set_status(404, "No %s with key %s" % (typ, self.params['key']));


class LoginHandler(handler.Handler):
	def post(self):
		logging.debug("Someone tried to login")
		
		if not self.requiredParams('username'):
			return
		
		data = self.request.body
		data = yamlhelper.loadData(data)
		
		query = dbmodel.User.gql("WHERE username = :1", self.params['username'])
		
		user = query.get()
		
		if not user:
			logging.info("Bad username '%s'" % self.params['username'])
			self.response.set_status(400, "Bad username or password")
			return

		try:
			if user.password != data['password']:
				logging.info("Bad password")
				self.response.set_status(400, "Bad username or password")
				return
		except KeyError:
			logging.info("No password")
			self.response.set_status(400, "No password given")
			return

		#Make sure the session id isn't already in use
		sID = str(uuid.uuid4())
		query = dbmodel.User.gql("WHERE currentSID = '%s'" % sID)
		while query.count() > 0:
			sID = str(uuid.uuid4())
			query = dbmodel.User.gql("WHERE currentSID = '%s'" % sID)

		user.currentSID = sID
		user.put()

		body = user.dump()

		self.response.out.write(body)

class SearchHandler(handler.Handler):
	def post(self):
		if not self.requiredParams('type'):
			self.response.set_status(400, "Missing required parameter 'type'")
			return

		typ = self.params['type']
		try:
			keys = dbmodel.types[typ].search(self.request.body)
		except KeyError:
			self.response.set_status(400, "Invalid type '%s'" % typ)
			return
		
		body = yamlhelper.dumpObj(keys)
		
		self.response.out.write(body)

class FindHandler(handler.Handler):
	def post(self):
		if not self.requiredParams('type'):
			self.response.set_status(400, "Missing required parameter 'type'")
			return
		
		typ = self.params['type']
		try:
			objs = dbmodel.types[typ].find(self.request.body)
		except KeyError:
			self.response.set_status(400, "Invalid type '%s'" % typ);
			return;
		
		body = yamlhelper.dumpObj(objs);
		self.response.write(body)

class UpdateHandler(handler.Handler):
	def post(self):

		if not self.requiredParams('key', 'type', 'sID'):
			logging.info("Required param missing in insert: "+str(self.params))
			return

		data = self.request.body

		typ = self.params['type']
		try:
			obj = dbmodel.types[typ].get(self.params["key"])
			obj.update(data, self.user)
		except KeyError:
			logging.info("Invalid type given in insert: "+typ)
			self.response.set_status(400, "Invalid type, "+typ)
			return
		except NotUniqueError, e:
			logging.info("Key not unique: "+e.key)
			self.response.set_status(400, "Key not unique: "+e.key)
			return

		self.response.set_status(200)


class SubStructSearchHandler(handler.Handler):
	def post(self):

		data = self.request.body
		query = dbmodel.Compound.all(keys_only=True)

		fp = yamlhelper.loadData(data)

		query = dbmodel.Compound.all(keys_only=True)
		for i in fp:
			query.filter("fingerprint =", i)

		logging.info(str(fp))
		logging.info(query)

		keys = query.fetch(100)
		keys = [str(i) for i in keys]
		logging.info(keys)

		self.response.out.write(yamlhelper.dumpObj(keys))


class GetReactionsHandler(handler.Handler):
	def get(self):
		if not self.requiredParams('key'):
			return

		try:
			key = db.Key(self.params['key'])
		except db.BadKeyError:
			self.response.set_status(400, "Key: "+self.params['key']+" invalid")
			return

		#Return the reactions where this is a reactant
		c = dbmodel.Compound.get(key)
		query = db.GqlQuery('SELECT * FROM Reactant WHERE compound = :1', c)
		reactants = query.fetch(100)

		#Return the reactions where this is a product
		c = dbmodel.Compound.get(key)
		query = db.GqlQuery('SELECT * FROM Product WHERE compound = :1', c)
		products = query.fetch(100)

		keysReactant = [str(r.reaction.key()) for r in reactants]
		keysProduct = [str(p.reaction.key()) for p in products]

		logging.info(str(keysReactant)+str(keysProduct))

		resp = {'reactant': keysReactant, 'product': keysProduct}
		body = yamlhelper.dumpObj(resp)

		self.response.out.write(body)

class FilterReactionsHandler(handler.Handler):
	def post(self):
		if not self.requiredParams():
			return

		data = yamlhelper.loadData(self.request.body)

		if 'filters' not in data:
			logging.info("Filters not provided")
			self.response.set_status(400, "Filters not provided")
			return

		sets = []
		logging.info("Received: "+str(data))
		for f in data['filters']:
			for t in ['reactants', 'products']:
				for key in f[t]:
					c = dbmodel.Compound.get(key)
					if not c:
						c = dbmodel.Reactant.get(key).compound
					logging.info("Found: "+str(c)+t)
					if t == 'reactants':
						equC = c.compound_reactants
					if t == 'products':
						equC = c.compound_products
					logging.info("Found: "+str(equC.fetch(100)))
					reactions = [r.reaction for r in equC]
					logging.info("Found: "+str(reactions))
					sets.append(set([r.key() for r in reactions]))

			logging.info("Sets: "+str(sets))
			for key in f['catalysts']:
				c = dbmodel.Catalyst.get(key)
				query = db.GqlQuery("SELECT __key__ FROM Reaction WHERE catalysts = :1", c)
				reactions = query.fetch(100)
				logging.info("Found: "+str(reactions))
				sets.append(set(reactions))

			logging.info("Sets: "+str(sets))
			for t in ['temperature', 'time', 'pH', 'pressure']:
				val = f[t]
				if not val:
					continue

				if f["max"+t]:
					query = db.GqlQuery("SELECT __key__ FROM Reaction WHERE "+ t +" <= :1", val)
				else:
					query = db.GqlQuery("SELECT __key__ FROM Reaction WHERE "+ t +" >= :1", val)

				reactions = query.fetch(1000)
				logging.info("Found: "+str(reactions))
				sets.append(set(reactions))

			logging.info("Sets: "+str(sets))
			for t in ['liquid', 'gas', 'solid']:
				if f[t]:
					query = db.GqlQuery("SELECT __key__ FROM Reaction WHERE "+ t +" = :1", f[t])
					reactions = query.fetch(1000)
					logging.info("Found: "+str(reactions))
					sets.append(set(reactions))

			logging.info("Sets: "+str(sets))
			if f['solvent']:
				query = db.GqlQuery("SELECT __key__ FROM Reaction WHERE solvent = :1", f['solvent'])
				reactions = query.fetch(1000)
				logging.info("Found: "+str(reactions))
				sets.append(set(reactions))
		if sets:
			ans = sets[0]
			for s in sets[1:]:
				ans &= s

			resp = [str(k) for k in ans]

			body = yamlhelper.dumpObj(resp)
		else:
			body = ""

		self.response.out.write(body)


class DeleteAllHandler(handler.Handler):
	def get(self):
		if self.request.url.find("origindb-test")==-1 and self.request.url.find(":8080")==-1:
			self.response.set_status(404)

		if not self.requiredParams('type'):
			return

		typ = self.params['type']
		query = dbmodel.types[typ].all()
		for i in query.fetch(1000):
			logging.info("deleting key"+str(i.key()))
			i.delete()

		self.response.set_status(200)


class ChangePasswordHandler(handler.Handler):
	def post(self):
		if not self.requiredParams('sID'):
			return

		data = self.request.body
		data = yamlhelper.loadData(data)

		if self.user.password != data['oldPassword']:
			self.response.set_status(400, "Wrong Password")
			return
		self.user.password = data['newPassword']

		self.user.put()

		self.response.set_status(200)


class GetUserHandler(handler.Handler):
	def get(self):
		if not self.requiredParams('sID'):
			return

		body = self.user.dump()

		self.response.out.write(body)


class LogoutHandler(handler.Handler):
	def get(self):
		if not self.requiredParams('sID'):
			return

		self.user.currentSID = ''
		self.response.set_status(200)

class RebuildSearchIndexingHandler(handler.Handler):
    """Rebuilds all indices for full text"""
    def get(self):
		logging.info("Full text index rebuild called from URL");
		
		# I would prefer to make this a query of all classes which are Searchable
		dbmodel.User.build_full_index()
		dbmodel.Compound.build_full_index()
		dbmodel.Reference.build_full_index()
		dbmodel.Catalyst.build_full_index()
		dbmodel.Pathway.build_full_index()
		
		logging.info("Rebuild complete");
		
		self.response.out.write("Search indexes fully rebuilt.")


application = webapp.WSGIApplication([('/', MainHandler),
                                      ('/insert', InsertHandler),
                                      ('/get', GetHandler),
                                      ('/login', LoginHandler),
                                      ('/logout', LogoutHandler),
                                      ('/search', SearchHandler),
                                      ('/find', FindHandler),
                                      ('/filterReactions', FilterReactionsHandler),
                                      ('/update', UpdateHandler),
                                      ('/subStructSearch', SubStructSearchHandler),
                                      ('/getReactions', GetReactionsHandler),
                                      ('/deleteAll', DeleteAllHandler),
                                      ('/changePassword', ChangePasswordHandler),
                                      ('/getUser', GetUserHandler),
                                      ('/tasks/searchindexing', search.SearchIndexing),
                                      ('/tasks/rebuildsearch', RebuildSearchIndexingHandler)
                                      ], debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()
